
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>13 实用功能 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch14.html" />
    
    
    <link rel="prev" href="ch12.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >13 实用功能</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="163"></a></p>
<div class="chapter-number"><p class="chapter-number">13</p></div>

<h1 id="utilities">实用功能 </h1>
<blockquote>
<p>你乐于挥霍的时间，都不能算作浪费。</p>
<p><span title="此引言的出处略复杂，详情请见 https://quoteinvestigator.com/2010/06/11/time-you-enjoy/ ，译法取自网络。">—— 伯特兰·罗素</span><sup><a href="#fn_1" id="reffn_1">1</a></sup></p>
</blockquote>
<h2 id="13.1">13.1 导言 </h2>
<p>并非所有标准库组件都置身于“容器”或“I/O”这样显而易见的分类中。
本章节将为那些短小但用途广泛的组件给出使用范例。
因为这些组件（类或模板）在描述设计和编程的时候被用作基本词汇，
它们也常被称为<em>词汇表类型（vocabulary types）</em>。
对于那些更强大的库设施——包括标准库的其它组件而言，这些库组件通常充当零件。
一个实用的函数或者类型，无需很复杂，也没必要跟其它函数和类搞得如胶似漆的。</p>
<p><a class="en-page-number" id="164"></a></p>
<h2 id="13.2">13.2 资源管理 </h2>
<p>只要不是小打小闹的程序，其关键任务之一就是管理资源。
资源就是需要先申请且使用后进行（显示或隐式）释放的东西。
比方说内存、锁、套接字、线程执柄以及文件执柄等等。
对于长时间运行的程序，不及时释放资源（“泄漏”）会导致严重的性能下降，
更有甚者还可能崩溃得让人心力交瘁的。
即便是较短的程序，资源泄露也会弄得很尴尬，
比方说因为资源不足而导致运行时间增加几个数量级。</p>
<p>标准库组件在设计层面力求避免资源泄露。
为此，它们依赖成对的 构造函数/析构函数 这种基础语言支持，
以确保资源随着持有它的对象一起释放。
<code>Vector</code>中管理器元素生命期的 构造函数/析构函数对 就是个范例（§4.2.2），
所有标准库容器都是以类似方式实现的。
重要的是，这个方法跟利用异常的错误处理相辅相成。
例如，以下技法用于标准库的锁类：</p>
<pre><code class="lang-cpp">mutex m;    <span class="hljs-comment">// used to protect access to shared data</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">()</span>
</span>{
    scoped_lock&lt;mutex&gt; lck {m}; <span class="hljs-comment">// acquire the mutex m</span>
    <span class="hljs-comment">// ... manipulate shared data ...</span>
}
</code></pre>
<p>在<code>lck</code>的构造函数获取<code>mutex</code>（§15.5）之前，该<code>thread</code>不会继续执行。
对应的析构函数会释放此资源。
因此在本例中，在控制线程离开<code>f()</code>时（<code>return</code>、“经由函数末尾”或抛出异常），
<code>scoped_lock</code>的析构函数会释放<code>mutex</code>。</p>
<p>这是 RAII（“资源请求即初始化”技术；§4.2.2）的一个应用。
RAII是C++中约定俗成管理资源方式的基础。
容器（例如<code>vector</code>、<code>map</code>、<code>string</code>和<code>iostream</code>）
就以类似的方式管理其资源（比如文件执柄和缓存）。</p>
<h3 id="13.2.1">13.2.1 <code>unique_ptr</code> 和 <code>shared_ptr</code> </h3>
<p>到目前为止的示例都仅涉及作用域内的对象，申请的资源在离开作用域时被释放，
那么分配在自由存储区域内的对象又改怎么处理呢？
在<code>&lt;memory&gt;</code>中，标准库提供了两个“智能指针”用于管理自由存储区中的对象：</p>
<ul>
<li>[1] <code>unique_ptr</code>用于独占所有权</li>
<li>[2] <code>shared_ptr</code>用于共享所有权</li>
</ul>
<p>这些“智能指针”最基本的用途是避免粗心大意导致的内存泄漏。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span> i, <span class="hljs-type">int</span> j)</span> <span class="hljs-comment">// X* vs. unique_ptr&lt;X&gt;</span>
</span>{
    X* p = <span class="hljs-keyword">new</span> X;               <span class="hljs-comment">// new一个X</span>
    unique_ptr&lt;X&gt; sp {<span class="hljs-keyword">new</span> X};   <span class="hljs-comment">// new一个X，并将其指针交给unique_ptr</span>
    <span class="hljs-comment">// ...</span>
</code></pre>
<p><a class="en-page-number" id="165"></a></p>
<pre><code class="lang-cpp">    <span class="hljs-keyword">if</span> (i&lt;<span class="hljs-number">99</span>) <span class="hljs-keyword">throw</span> Z{};        <span class="hljs-comment">// 可能会抛出异常</span>
    <span class="hljs-keyword">if</span> (j&lt;<span class="hljs-number">77</span>) <span class="hljs-keyword">return</span>;           <span class="hljs-comment">// 可能会“提前”return</span>
    <span class="hljs-comment">// ... 使用 p 和 sp ..</span>
    <span class="hljs-keyword">delete</span> p;   <span class="hljs-comment">// 销毁 *p</span>
}
</code></pre>
<p>此处，如果<code>i&lt;99</code>或<code>j&lt;77</code>，我们就“忘记了”删除<code>p</code>。
另一方面，无论以怎样的方式离开<code>f()</code>（抛出异常、执行<code>return</code>或“经由函数末尾”），
<code>unique_ptr</code>都会确保其对象妥善销毁。
搞笑的是，这个问题本可以轻易就解决掉，只要<em>不</em>使用指针且<em>不</em>使用<code>new</code>就行：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span> i, <span class="hljs-type">int</span> j)</span> <span class="hljs-comment">// 使用局部变量</span>
</span>{
    X x;
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>不幸的是，<code>new</code>（以及指针和引用）的滥用是个日益严重的问题。</p>
<p>无论如何，在你切实需要使用指针语意的时候，与努力把裸指针用对相比，
<code>unique_ptr</code>是个轻量级机制，没有额外空间和时间开销。
它的一个进阶用法是，把分配在自由存储区的对象传入或传出函数：</p>
<pre><code class="lang-cpp"><span class="hljs-function">unique_ptr&lt;X&gt; <span class="hljs-title">make_X</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>
    <span class="hljs-comment">// 分配一个X并直接交给一个unique_ptr</span>
</span>{
    <span class="hljs-comment">// ... 检查i，以及其它操作. ...</span>
    <span class="hljs-keyword">return</span> unique_ptr&lt;X&gt;{<span class="hljs-keyword">new</span> X{i}};
}
</code></pre>
<p><code>unique_ptr</code>是个单个对象（或数组）的执柄，
这跟<code>vector</code>作为对象序列的执柄如出一辙。
都（借由RAII）控制其它对象的生命期，也都依赖于转移语意实现简洁高效的<code>return</code>。</p>
<p><code>shared_ptr</code>跟<code>unique_ptr</code>相似，区别是<code>shared_ptr</code>被复制而非被转移。
某个对象的所有<code>shared_ptr</code>共享其所有权；这些<code>shared_ptr</code>全部销毁时，
该对象也随之销毁。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(shared_ptr&lt;fstream&gt;)</span></span>;
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">g</span><span class="hljs-params">(shared_ptr&lt;fstream&gt;)</span></span>;

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">(<span class="hljs-type">const</span> string&amp; name, ios_base::openmode mode)</span>
</span>{
    shared_ptr&lt;fstream&gt; fp {<span class="hljs-keyword">new</span> <span class="hljs-built_in">fstream</span>(name,mode)};
    <span class="hljs-keyword">if</span> (!*fp)   <span class="hljs-comment">// 确保文件打开正常</span>
        <span class="hljs-keyword">throw</span> No_file{};

    <span class="hljs-built_in">f</span>(fp);
    <span class="hljs-built_in">g</span>(fp);
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><a class="en-page-number" id="166"></a></p>
<p>此处，由<code>fp</code>的构造函数打开的文件会确保被关闭
——在最后一个函数（显示或隐式地）销毁<code>fp</code>副本的时候。
请留意，<code>f()</code>或<code>g()</code>可能创建任务或以其它方式保存<code>fp</code>的一份副本，
该副本的生命期可能比<code>user()</code>要长。
这样，<code>shared_ptr</code>就提供了一种垃圾回收形式，
它遵守 内存受管对象 基于析构函数的资源管理方式。
这个代价既非没有成本，又不至于高到离谱，
但它确实导致了 共享对象的生命期难以预测 的问题。
请只在确定需要共享所有权的时候再使用<code>shared_ptr</code>。</p>
<p>先在自由存储区上创建对象，然后再把它的指针传给某个智能指针，这套操作有些繁琐。
而且还容易出错，比如忘记把指针传给<code>unique_ptr</code>，
或者把非自由存储区上的对象指针传给了<code>shared_ptr</code>。
为避免这些问题，标准库（在<code>&lt;memory&gt;</code>中）为 构造对象并返回相应智能指针
提供了函数<code>make_shared()</code>和<code>make_unique()</code>。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">S</span> {
    <span class="hljs-type">int</span> i;
    string s;
    <span class="hljs-type">double</span> d;
    <span class="hljs-comment">// ...</span>
};

<span class="hljs-keyword">auto</span> p1 = <span class="hljs-built_in">make_shared</span>&lt;S&gt;(<span class="hljs-number">1</span>,<span class="hljs-string">&quot;Ankh Morpork&quot;</span>,<span class="hljs-number">4.65</span>);    <span class="hljs-comment">// p1是个shared_ptr&lt;S&gt;</span>
<span class="hljs-keyword">auto</span> p2 = <span class="hljs-built_in">make_unique</span>&lt;S&gt;(<span class="hljs-number">2</span>,<span class="hljs-string">&quot;Oz&quot;</span>,<span class="hljs-number">7.62</span>);              <span class="hljs-comment">// p2是个unique_ptr&lt;S&gt;</span>
</code></pre>
<p>此处<code>p2</code>是个<code>unique_ptr&lt;S&gt;</code>，指向分配在自由存储区上的<code>S</code>类型对象，
该对象的值为<code>{2,&quot;Oz&quot;s,7.62}</code>。</p>
<p>跟先使用<code>new</code>去构造对象，然后传给<code>shared_ptr</code>的分步操作相比，
<code>make_shared()</code>不光是便利，其效率也明显更好，
因为它不需要为引用计数单独执行一次内存分配，
对于<code>shared_ptr</code>的实现来说，引用计数是必须的。</p>
<p>有了<code>unique_ptr</code>和<code>shared_ptr</code>的帮助，就可以给很多程序实现一个彻底
“无裸<code>new</code>(no naked <code>new</code>)”的策略（§4.2.2）。
不过这些“智能指针”在概念上仍然是指针，因此在资源管理方面只能是次选
——首选是 容器 和 其它在更高概念层级上进行资源管理的类型。
尤其是，<code>shared_ptr</code>本身并未为它的所有者提供针对共享对象的 读 和/或 写 规则。
仅仅消除了资源管理问题，并不能解决数据竞争（§15.7）和其它形式的混乱。</p>
<p>我们该在何处使用（诸如<code>unique_ptr</code>的）“智能指针”，
而非带有专门为资源设计过操作的资源执柄（比如<code>vector</code>或<code>thread</code>）呢？
答案在意料之中，是“在我们需要指针语意的时候”。</p>
<ul>
<li>在共享一个对象时，需要指针（或引用）来指向被共享的对象，
  于是<code>shared_ptr</code>就成为顺理成章的选择（除非有个显而易见的唯一所有者）。</li>
<li>在经典的面向对象代码（§4.5）中指向一个多态对象时，需要使用指针（或引用），
  因为被引用对象的具体类型（甚至其容量）未知，因此<code>unique_ptr</code>就是个显而易见的选择。</li>
<li>被共享的多态对象通常需要<code>shared_ptr</code>。</li>
</ul>
<p>从函数中返回对象的集合时，<em>不</em>需要使用指针；
容器在这个情形下作为资源执柄，即简单又高效（§5.2.2）。</p>
<p><a class="en-page-number" id="167"></a></p>
<h3 id="13.2.2">13.2.2 move() 和 forward() </h3>
<p>转移和复制之间的选择通常是隐式的（§3.6）。
在对象将被销毁时（如<code>return</code>），编译器倾向于采用转移，
因为这是更简洁、更高效的操作。
不过，有时候不得不显式指定。例如：某个<code>unique_ptr</code>是一个对象的唯一所有者。
因此，它无法被复制：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f1</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">auto</span> p = <span class="hljs-built_in">make_unique</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-number">2</span>);
    <span class="hljs-keyword">auto</span> q = p;         <span class="hljs-comment">// 报错：无法复制 unique_ptr</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>如果某个<code>unique_ptr</code>需要去在别处，就必须转移它。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f1</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">auto</span> p = <span class="hljs-built_in">make_unique</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-number">2</span>);
    <span class="hljs-keyword">auto</span> q = <span class="hljs-built_in">move</span>(p);       <span class="hljs-comment">// p 现在持有 nullptr</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>令人费解的是，<code>std::move()</code>并不会移动任何东西。
相反，它只是把它的参数转换成一个右值引用，
以此说明其参数不会再被使用，因此可以被转移（§5.2.2）。
它本应该取一个其它的函数名，比如<code>rvalue_cast()</code>。
跟其它类型转换相似，它也容易出错，最好避免使用。
它为某些特定情况而存在。参考以下这个简单的互换：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">swap</span><span class="hljs-params">(T&amp; a, T&amp; b)</span>
</span>{
    T tmp {<span class="hljs-built_in">move</span>(a)};    <span class="hljs-comment">// T 的构造函数见到右值并转移</span>
    a = <span class="hljs-built_in">move</span>(b);        <span class="hljs-comment">// T 的赋值见到右值并转移</span>
    b = <span class="hljs-built_in">move</span>(tmp);      <span class="hljs-comment">// T 的赋值见到右值并转移</span>
}
</code></pre>
<p>我们不想反复复制可能容量很大的对象，因此用<code>std::move()</code>进行转移。</p>
<p>跟其它类型转换相似，<code>std::move()</code>很诱人但也很危险。参考：</p>
<pre><code class="lang-cpp">string s1 = <span class="hljs-string">&quot;Hello&quot;</span>;
string s2 = <span class="hljs-string">&quot;World&quot;</span>;
vector&lt;string&gt; v;
v.<span class="hljs-built_in">push_back</span>(s1);        <span class="hljs-comment">// 使用&quot;const string&amp;&quot;参数；push_back()将进行复制</span>
v.<span class="hljs-built_in">push_back</span>(<span class="hljs-built_in">move</span>(s2));  <span class="hljs-comment">// 使用转移构造函数</span>
</code></pre>
<p>此处<code>s1</code>被（通过<code>push_back()</code>）复制，而<code>s2</code>则是被转移。
这（仅仅是）有时候会让<code>s2</code>的<code>push_back()</code>更节约些。
问题是这里遗留的移出对象（moved-from object）。如果再次使用<code>s2</code>就会出问题：</p>
<pre><code class="lang-cpp">cout &lt;&lt; s1[<span class="hljs-number">2</span>]; <span class="hljs-comment">// 输出 ’l’</span>
cout &lt;&lt; s2[<span class="hljs-number">2</span>]; <span class="hljs-comment">// 崩溃？</span>
</code></pre>
<p>我认为，如果<code>std::move()</code>被广泛应用，太容易出错。</p>
<p><a class="en-page-number" id="168"></a></p>
<p>除非能证明有显著和必要的性能提升，否则别用它。
后续维护很可能不经意就意想不到地用到了移出对象。</p>
<p>移出对象的状态通常来说是未指定的，但是所有标准库类型都会使之可销毁并且可被赋值。
如果不遵循这一惯例，可就不太明智了。
对于容器（如<code>vector</code>或<code>string</code>），移出状态将会是“置空”。
对于许多类型，默认值就是个很好的空状态：有意义且构建的成本低廉。</p>
<p>参数转发是个转移操作的重要用途（§7.4.2）。
有时候，我们需要把一套参数原封不动地传递给另一个函数
（以达成“完美转发（perfect forwarding）”）：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span>... Args&gt;
unique_ptr&lt;T&gt; <span class="hljs-title">make_unique</span><span class="hljs-params">(Args&amp;&amp;... args)</span>
</span>{
    <span class="hljs-keyword">return</span> unique_ptr&lt;T&gt;{<span class="hljs-keyword">new</span> T{std::forward&lt;Args&gt;(args)...}};   <span class="hljs-comment">// 转发每个参数</span>
}
</code></pre>
<p>标准库<code>std::forward()</code>与简易的<code>std::move()</code>区别在于，
它能够正确处理左值和右值间细微的差异（§5.2.2）。
请仅在转发的时候使用<code>std::forward()</code>，并且别把任何东西<code>forward()</code>两次；
一旦你转发过某个对象，它就不再属于你了。</p>
<h2 id="13.3">13.3 区间检查：<code>gsl::span</code> </h2>
<p>按以往经验，越界错误是C和C++程序中严重错误的一个主要来源。
容器（第11章）、算法（第12章）和区间-<code>for</code>的使用显著缓解了这个问题，
但仍有改进的余地。
越界错误的一个关键原因在于人们传递指针（裸指针或智能指针），
而后依赖惯例去获取所指向元素的数量。
对于资源执柄以外的代码，有个颠扑不破的建议是：
假设所指向的对象至多有一个[CG: F.22]，
但如果没有足够的支持，这个建议也不太好操作。
标准库的<code>string_view</code>（§9.3）能帮点忙，但它是只读的，并且只支持字符。
大多数程序员需要更多的支持。</p>
<p>C++ Core Guidelines [Stroustrup,2015] 提供了指导以及一个小型的
指导支持库（Guidelines Support Library）[GSL]，
其中有个<code>span</code>类型用于指向包含元素的区间。
这个<code>span</code>已经向标准提交，但如果目前需要它，
<a href="https://en.cppreference.com/w/cpp/container/span" title="翻译时，它已经进入C++20，定义在头文件&lt;span&gt;里，位于命名空间std下" target="_blank">
只能下载使用
</a>。
大体上，<code>span</code>是个表示元素序列的(指针,长度)对：</p>
<p><img src="img/ch13_01.png" alt="span"></p>
<p><code>span</code>为 包含元素的连续序列 提供访问。
其中的元素可以按多种形式存储，包括<code>vector</code>和内建数组。
与指针相仿，<code>span</code>对其指向的内容并无所有权。
在这方面，它与<code>string_view</code>（§9.3）和STL的迭代器对（§12.3）如出一辙。</p>
<p><a class="en-page-number" id="169"></a></p>
<p>考虑这个常见的接口风格：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fpn</span><span class="hljs-params">(<span class="hljs-type">int</span>* p, <span class="hljs-type">int</span> n)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i&lt;n; ++i)
        p[i] = <span class="hljs-number">0</span>;
}
</code></pre>
<p>此处假设<code>p</code>指向<code>n</code>个整数。
遗憾的是，这种假设仅仅是个惯例，因此无法用于区间-<code>for</code>循环，
编译器也没办法实现一个低消耗高性能的越界检查。
此外，这个假设还会出错：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">use</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span>
</span>{
    <span class="hljs-type">int</span> a[<span class="hljs-number">100</span>];
    <span class="hljs-built_in">fpn</span>(a,<span class="hljs-number">100</span>);     <span class="hljs-comment">// OK</span>
    <span class="hljs-built_in">fpn</span>(a,<span class="hljs-number">1000</span>);    <span class="hljs-comment">// 我艹，手滑了！（fpn里越界错误）</span>
    <span class="hljs-built_in">fpn</span>(a+<span class="hljs-number">10</span>,<span class="hljs-number">100</span>);  <span class="hljs-comment">// fpn里越界错误</span>
    <span class="hljs-built_in">fpn</span>(a,x);       <span class="hljs-comment">// 可疑，但看上去无害</span>
}
</code></pre>
<p>可利用<code>span</code>对它进行改进：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fs</span><span class="hljs-params">(span&lt;<span class="hljs-type">int</span>&gt; p)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span>&amp; x : p)
        x = <span class="hljs-number">0</span>;
}
</code></pre>
<p><code>fs</code>用法如下：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">use</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span>
</span>{
    <span class="hljs-type">int</span> a[<span class="hljs-number">100</span>];
    <span class="hljs-built_in">fs</span>(a);          <span class="hljs-comment">// 隐式创建一个 span&lt;int&gt;{a,100} </span>
    <span class="hljs-built_in">fs</span>(a,<span class="hljs-number">1000</span>);     <span class="hljs-comment">// 报错：需要一个 span</span>
    <span class="hljs-built_in">fs</span>({a+<span class="hljs-number">10</span>,<span class="hljs-number">100</span>}); <span class="hljs-comment">// fs里报越界访问错误</span>
    <span class="hljs-built_in">fs</span>({a,x});      <span class="hljs-comment">// 明显很可疑</span>
}
</code></pre>
<p>此处，最常见的情形是直接由数组创建一个<code>span</code>，
于是获得了安全性（编译器计算元素数量）以及编码的简洁性。
至于其它情形，出错的概率也降低了，因为程序员不得不显式构造一个span。</p>
<p>对于在函数间进行传递这种常见情形，<code>span</code>要比(指针,数量)接口简洁，
而且很明显还无需额外的越界检查：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f1</span><span class="hljs-params">(span&lt;<span class="hljs-type">int</span>&gt; p)</span></span>;

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f2</span><span class="hljs-params">(span&lt;<span class="hljs-type">int</span>&gt; p)</span>
</span>{
    <span class="hljs-comment">// ...</span>
    <span class="hljs-built_in">f1</span>(p);
}
</code></pre>
<p><a class="en-page-number" id="170"></a></p>
<p>在用于取下标操作（即<code>r[i]</code>）时，会执行越界检查，
在触发越界错误情况下会抛出<code>gsl::fail_fast</code>。
对于极度追求性能的代码，越界检查是可以禁掉的。
在<code>span</code>被纳入标准时，我期待<code>std::span</code>能够按照
[Garcia,2016] [Garcia,2018]中的约定去管理针对越界访问的响应。</p>
<p>请留意，对于循环来说，越界检查只需要进行一次。
因此，对于函数体仅仅是 针对<code>span</code>的循环 这种常见情形而言，越界访问几乎没有开销。</p>
<p>针对字符的<code>span</code>直接获得支持，并且名称是<code>gsl::string_span</code>。</p>
<h2 id="13.4">13.4 专用容器 </h2>
<p>标准库提供了几个未能全然符合STL框架的容器（第11章、第12章）。
比方说内建数组、<code>array</code>和<code>string</code>。
我有时候称之为“次容器（almost containers）”，但这么说有点略失公允：
它们承载了元素，因此也算是容器，但由于各自的局限性或附带的便利性，
使它们在STL的语境中略失和谐。将其单独介绍有助于简化STL的叙述。</p>
<table style="width:90%;margin-left:auto;margin-right:auto;">
    <tbody>
        <tr>
            <th colspan="2" style="text-align: center">
                <strong>“次容器”</strong><br>
            </th>
        </tr>
        <tr>
            <td style="width:15%"><code>T[N]</code></td>
            <td>内建数组：定长且连续分配的<code>N</code>个<code>T</code>类型元素的序列；隐式转化为<code>T*</code></td>
        </tr>
        <tr>
            <td><code>array&lt;T,N&gt;</code></td>
            <td>定长且连续分配的<code>N</code>个<code>T</code>类型元素的序列；与内建数组类似，但消除了大部分问题</td>
        </tr>
        <tr>
            <td><code>bitset&lt;N&gt;</code></td>
            <td>定长的<code>N</code>个二进制位的序列</td>
        </tr>
        <tr>
            <td><code>vector&lt;bool&gt;</code></td>
            <td>一个以特化<code>vector</code>进行存储的紧凑二进制位的序列</td>
        </tr>
        <tr>
            <td><code>pair&lt;T,U&gt;</code></td>
            <td>包含<code>T</code>和<code>U</code>类型的两个元素</td>
        </tr>
        <tr>
            <td><code>tuple&lt;T...&gt;</code></td>
            <td>任意元素数量且元素间类型可各不相同的序列</td>
        </tr>
        <tr>
            <td><code>basic_string&lt;C&gt;</code></td>
            <td>字符类型为<code>C</code>的序列；提供字符串操作</td>
        </tr>
        <tr>
            <td><code>valarray&lt;T&gt;</code></td>
            <td>数值类型为<code>T</code>的数组；支持数值运算</td>
        </tr>
    </tbody>
</table>

<p>标准库为何要提供这么多容器呢？它们针对的是普遍却略有差异（常有交叠）的需求。
如果标准库未能提供，许多人就不得不造轮子。例如：</p>
<ul>
<li><code>pair</code>和<code>tuple</code>是异质的；其它所有容器都是同质的（所有元素类型都相同）。</li>
<li><code>array</code>、<code>vector</code>和<code>tuple</code>是连续分配的；
  <code>forward_list</code>和<code>map</code>都是链式结构。</li>
<li><code>bitset</code>和<code>vector&lt;bool&gt;</code>承载二进制位且通过代理对象提供访问；
  所有其它标准库容器都承载各种类型的元素且提供直接访问。</li>
<li><code>basic_string</code>规定其元素类型是某种形式的字符且提供字符串操作，
  比如字符串连接以及语境相关的操作。</li>
<li><code>valarray</code>要求其元素是数字且提供数值运算。</li>
</ul>
<p>这些容器都提供特定的服务，这些服务对诸多程序员社群是必不可少的。
这些需求无法用单一容器满足，因为其中某些需求相互矛盾，例如：
“长度可增”和“确保分配在特定位置”就相互矛盾，
“添加元素时元素位置不可变”和“分配必须连续”也是。</p>
<p><a class="en-page-number" id="171"></a></p>
<h3 id="13.4.1">13.4.1 <code>array</code> </h3>
<p>定义在<code>&lt;array&gt;</code>中的<code>array</code>是个给定类型的定长序列，
其中的元素数量要在编译期指定。
因此，<code>array</code>的元素可以分配在栈上，在对象里或者在静态存储区。
元素被分配的作用域包含了该<code>array</code>的定义。
<code>array</code>的最佳理解方式是看做内置数组，其容量不能改变，
但不会被隐式、令人略感诧异地转换成指针类型，它还提供了几个便利的函数。
与内建数组相比，使用<code>array</code>不会带来（时间或空间上的）开销。
<code>array</code>并<em>不</em>遵循STL容器模型的“元素执柄”模型。
相反，<code>array</code>直接包含了它的元素。</p>
<p><code>array</code>可以用初始化列表进行初始化：</p>
<pre><code class="lang-cpp">array&lt;<span class="hljs-type">int</span>,3&gt; a1 = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>};
</code></pre>
<p>初始化列表中的元素数量必须等于或小于为<code>array</code>指定的数量。</p>
<p>元素数量是必选项：</p>
<pre><code class="lang-cpp">array&lt;<span class="hljs-type">int</span>&gt; ax = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>}; <span class="hljs-comment">// 报错，未指定容量</span>
</code></pre>
<p>元素数量必须是常量表达式：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span>
</span>{
    array&lt;string,n&gt; aa = {<span class="hljs-string">&quot;John&apos;s&quot;</span>, <span class="hljs-string">&quot;Queens&apos; &quot;</span>};    <span class="hljs-comment">// 报错：容量不是常量表达式</span>
    <span class="hljs-comment">//</span>
}
</code></pre>
<p>如果需要用变量作为元素数量，请使用<code>vector</code>。</p>
<p>在必要的时候，<code>array</code>可以被显式地传给要求指针的C-风格函数。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">int</span>* p, <span class="hljs-type">int</span> sz)</span></span>;     <span class="hljs-comment">// C-风格接口</span>

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">g</span><span class="hljs-params">()</span>
</span>{
    array&lt;<span class="hljs-type">int</span>,10&gt; a;

    <span class="hljs-built_in">f</span>(a,a.<span class="hljs-built_in">size</span>());          <span class="hljs-comment">// 报错：无隐式转换</span>
    <span class="hljs-built_in">f</span>(&amp;a[<span class="hljs-number">0</span>],a.<span class="hljs-built_in">size</span>());      <span class="hljs-comment">// C-风格用法</span>
    <span class="hljs-built_in">f</span>(a.<span class="hljs-built_in">data</span>(),a.<span class="hljs-built_in">size</span>());   <span class="hljs-comment">// C-风格用法 </span>

    <span class="hljs-keyword">auto</span> p = <span class="hljs-built_in">find</span>(a.<span class="hljs-built_in">begin</span>(),a.<span class="hljs-built_in">end</span>(),<span class="hljs-number">777</span>);   <span class="hljs-comment">// C++/STL-风格用法</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>有了更加灵活的<code>vector</code>，为什么还要用<code>array</code>呢？<code>array</code>不那么灵活，所以更简单。
偶尔，与 借助<code>vector</code>（执柄）访问自由存储区内的元素并将其回收 相比，
直接访问栈上分配的元素具有大幅度的性能优势。
反之，栈是个稀缺资源（尤其在嵌入式系统上），栈溢出也太恶心。</p>
<p><a class="en-page-number" id="172"></a></p>
<p>那么既然可以用内建数组，又何必用<code>array</code>呢？
<code>array</code>知悉其容量，因此易于搭配标准库算法使用，它还可以用<code>=</code>进行复制。
不过，我用<code>array</code>的主要原因是避免意外且恶心地隐式转换为指针。考虑：</p>
<pre><code class="lang-cpp">
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">h</span><span class="hljs-params">()</span>
</span>{
    Circle a1[<span class="hljs-number">10</span>];
    array&lt;Circle,10&gt; a2;
    <span class="hljs-comment">// ...</span>
    Shape* p1 = a1; <span class="hljs-comment">// OK：灾难即将发生</span>
    Shape* p2 = a2; <span class="hljs-comment">// 报错：没有从array&lt;Circle,10&gt;到Shape*的隐式转换</span>
    p1[<span class="hljs-number">3</span>].<span class="hljs-built_in">draw</span>();   <span class="hljs-comment">// 灾难</span>
}
</code></pre>
<p>带有“灾难”注释的那行代码假设<code>sizeof(Shape)&lt;sizeof(Circle)</code>，
因此借助<code>Shape*</code>去对<code>Circle[]</code>取下标就得到了错误的偏移。
所有标准库容器都都提供这个优于内建数组的益处。</p>
<h3 id="13.4.2">13.4.2 <code>bitset</code> </h3>
<p>系统的某个特性，例如输入流的状态，经常是一组标志位，以表示二元状态，
例如 好/坏、真/假、开/关。
C++支持针对少量标志位的高效表示，其方法要借助整数的位运算（§1.4）。
<code>bitset&lt;N&gt;</code>类泛化了这种表示法，提供了针对<code>N</code>个二进制位<code>[0:N)</code>的操作，
其中<code>N</code>要在编译期指定。
对于无法装进<code>long long int</code>的二进制位集合，
用<code>bitset</code>比直接使用整数方便很多。
对于较小的集合，<code>bitset</code>通常也优化得比较好。
如果需要给这些位命名而不使用编号，可以使用<code>set</code>（§11.4）或者枚举（§2.5）。</p>
<p><code>bitset</code>可以用整数或字符串初始化：</p>
<pre><code class="lang-cpp">bitset&lt;9&gt; bs1 {<span class="hljs-string">&quot;110001111&quot;</span>};
bitset&lt;9&gt; bs2 {<span class="hljs-number">0b1&apos;1000&apos;1111</span>}; <span class="hljs-comment">// 使用数字分隔符的二进制数字文本(§1.4)</span>
</code></pre>
<p>常规的位运算符（§1.4）以及左移与右移运算符（<code>&lt;&lt;</code>和<code>&gt;&gt;</code>）也可以用：</p>
<pre><code class="lang-cpp">bitset&lt;9&gt; bs3 = ~bs1        <span class="hljs-comment">// 取补： bs3==&quot;001110000&quot;</span>
bitset&lt;<span class="hljs-number">9</span>&gt; bs4 = bs1&amp;bs3;    <span class="hljs-comment">// 全零</span>
bitset&lt;9&gt; bs5 = bs1&lt;&lt;<span class="hljs-number">2</span>;     <span class="hljs-comment">// 向左移： bs5 = &quot;000111100&quot;</span>
</code></pre>
<p>移位运算符（此处的<code>&lt;&lt;</code>）会“移入（shift in）”零。</p>
<p><code>to_ullong()</code>和<code>to_string()</code>运算提供构造函数的逆运算。
例如，我们可以输出一个<code>int</code>的二进制表示：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">binary</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>
</span>{
    bitset&lt;8*<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>)&gt; b = i;    <span class="hljs-comment">// 假设字节为8-bit（详见 §14.7）</span>
    cout &lt;&lt; b.<span class="hljs-built_in">to_string</span>() &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;  <span class="hljs-comment">// 输出i的二进制位</span>
}
</code></pre>
<p>这段代码从左到右打印出<code>1</code>和<code>0</code>的二进制位，最高有效位在最左面，
因此对于参数<code>123</code>将有如下输出：</p>
<pre><code class="lang-cpp"><span class="hljs-number">00000000000000000000000001111011</span>
</code></pre>
<p><a class="en-page-number" id="173"></a></p>
<p>对此例而言，直接用<code>bitset</code>的输出运算符会更简洁：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">binary2</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span>
</span>{
    bitset&lt;8*<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>)&gt; b = i;    <span class="hljs-comment">// 假设字节为8-bit（详见 §14.7）</span>
    cout &lt;&lt; b &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;              <span class="hljs-comment">// 输出i的二进制位</span>
}
</code></pre>
<h3 id="13.4.3">13.4.3 <code>pair</code>和<code>tuple</code> </h3>
<p>我们时常会用到一些单纯的数据，也就是值的集合，
而非一个附带明确定义的语意以及 其值的不变式（§3.5.2）的类对象。
这种情况下，含有一组适当命名成员的简单<code>struct</code>通常就很理想了。
或者说，还可以让标准库替我们写这个定义。
例如，标准库算法<code>equal_range</code>返回一个表示符合某谓词子序列的迭代器<code>pair</code>：</p>
<pre><code class="lang-cpp">
<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Forward_iterator, <span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span> Compare&gt;
    pair&lt;Forward_iterator,Forward_iterator&gt;
    <span class="hljs-title">equal_range</span><span class="hljs-params">(Forward_iterator first, Forward_iterator last, <span class="hljs-type">const</span> T&amp; val, Compare cmp)</span></span>;
</code></pre>
<p>给定一个有序序列[<code>first</code>:<code>last</code>)，<code>equal_range</code>将返回一个<code>pair</code>，
以表示符合谓词<code>cmp</code>的子序列。
可以用它对一个承载<code>Record</code>的有序序列进行查找：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> less = [](<span class="hljs-type">const</span> Record&amp; r1, <span class="hljs-type">const</span> Record&amp; r2) { <span class="hljs-keyword">return</span> r1.name&lt;r2.name;}; <span class="hljs-comment">// compare names</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">const</span> vector&lt;Record&gt;&amp; v)</span> <span class="hljs-comment">// 假设v按照其“name”字段排序</span>
</span>{
    <span class="hljs-keyword">auto</span> er = <span class="hljs-built_in">equal_range</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),Record{<span class="hljs-string">&quot;Reg&quot;</span>},less);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p = er.first; p!=er.second; ++p)  <span class="hljs-comment">// 打印所有相等的记录</span>
        cout &lt;&lt; *p;                             <span class="hljs-comment">// 假设定义过Record的&lt;&lt;</span>
}
</code></pre>
<p><code>pair</code>的第一个成员被称为<code>first</code>，第二个成员被称为<code>second</code>。
该命名在创造力方面乏善可陈，乍看之下可能还有点怪异，
但在编写通用代码的时候，却能够从这种一致的命名中受益匪浅。
在<code>first</code>和<code>second</code>过于泛化的情况下，可借助结构化绑定（§3.6.3）：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f2</span><span class="hljs-params">(<span class="hljs-type">const</span> vector&lt;Record&gt;&amp; v)</span> <span class="hljs-comment">// 假设v按照其“name”字段排序</span>
</span>{
    <span class="hljs-keyword">auto</span> [first,last] = <span class="hljs-built_in">equal_range</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),Record{<span class="hljs-string">&quot;Reg&quot;</span>},less);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p = first; p!=last; ++p)  <span class="hljs-comment">// 打印所有相等的记录</span>
        cout &lt;&lt; *p;                     <span class="hljs-comment">// 假设定义过Record的&lt;&lt;</span>
}
</code></pre>
<p>标准库中（来自<code>&lt;utility&gt;</code>）的<code>pair</code>在标准库内外应用颇广。
如果其元素允许，<code>pair</code>就提供诸如<code>=</code>、<code>==</code>和<code>&lt;</code>这些运算符。
类型推导简化了<code>pair</code>的创建，使得无需再显式提及它的类型。例如：</p>
<p><a class="en-page-number" id="174"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(vector&lt;string&gt;&amp; v)</span>
</span>{
    pair p1 {v.<span class="hljs-built_in">begin</span>(),<span class="hljs-number">2</span>};              <span class="hljs-comment">// 一种方式</span>
    <span class="hljs-keyword">auto</span> p2 = <span class="hljs-built_in">make_pair</span>(v.<span class="hljs-built_in">begin</span>(),<span class="hljs-number">2</span>);   <span class="hljs-comment">// 另一种方式</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><code>p1</code>和<code>p2</code>的类型都是<code>pair&lt;vector&lt;string&gt;::iterator,int&gt;</code>。</p>
<p>如果所需的元素多于（或少于）两个，可以使用（来自<code>&lt;utility&gt;</code>的）<code>tuple</code>。
<code>tuple</code>是个 元素的异质序列，例如：</p>
<pre><code class="lang-cpp">tuple&lt;string,<span class="hljs-type">int</span>,<span class="hljs-type">double</span>&gt; t1 {<span class="hljs-string">&quot;Shark&quot;</span>,<span class="hljs-number">123</span>,<span class="hljs-number">3.14</span>};     <span class="hljs-comment">// 显式指定了类型</span>
<span class="hljs-keyword">auto</span> t2 = <span class="hljs-built_in">make_tuple</span>(string{<span class="hljs-string">&quot;Herring&quot;</span>},<span class="hljs-number">10</span>,<span class="hljs-number">1.23</span>);    <span class="hljs-comment">// 类型推导为tuple&lt;string,int,double&gt;</span>
tuple t3 {<span class="hljs-string">&quot;Cod&quot;</span>s,<span class="hljs-number">20</span>,<span class="hljs-number">9.99</span>};                          <span class="hljs-comment">// 类型推导为tuple&lt;string,int,double&gt;</span>
</code></pre>
<p>较旧的代码往往用<code>make_tuple()</code>，因为从构造函数推导模板参数类型是C++17的内容。</p>
<p>访问<code>tuple</code>成员需要用到<code>get()</code>函数模板：</p>
<pre><code class="lang-cpp">string s = <span class="hljs-built_in">get</span>&lt;<span class="hljs-number">0</span>&gt;(t1);  <span class="hljs-comment">// 获取第一个元素： &quot;Shark&quot;</span>
<span class="hljs-type">int</span> x = <span class="hljs-built_in">get</span>&lt;<span class="hljs-number">1</span>&gt;(t1);     <span class="hljs-comment">// 获取第二个元素： 123</span>
<span class="hljs-type">double</span> d = <span class="hljs-built_in">get</span>&lt;<span class="hljs-number">2</span>&gt;(t1);  <span class="hljs-comment">// 获取第三个元素： 3.14</span>
</code></pre>
<p><code>tuple</code>的成员是数字编号的（从零开始），并且下标必须是常数。</p>
<p>通过下标访问<code>tuple</code>很普遍、丑陋，还颇易出错。
好在，<code>tuple</code>中具有唯一类型的元素可在该<code>tuple</code>中以其类型“命名（named）”：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> s = <span class="hljs-built_in">get</span>&lt;string&gt;(t1);   <span class="hljs-comment">// 获取string： &quot;Shark&quot;</span>
<span class="hljs-keyword">auto</span> x = <span class="hljs-built_in">get</span>&lt;<span class="hljs-type">int</span>&gt;(t1);      <span class="hljs-comment">// 获取int： 123</span>
<span class="hljs-keyword">auto</span> d = <span class="hljs-built_in">get</span>&lt;<span class="hljs-type">double</span>&gt;(t1);   <span class="hljs-comment">// 获取double： 3.14</span>
</code></pre>
<p><code>get&lt;&gt;()</code>还可以进行写操作：</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">get</span>&lt;string&gt;(t1) = <span class="hljs-string">&quot;Tuna&quot;</span>;   <span class="hljs-comment">// 写入到string</span>
<span class="hljs-built_in">get</span>&lt;<span class="hljs-type">int</span>&gt;(t1) = <span class="hljs-number">7</span>;           <span class="hljs-comment">// 写入到int</span>
<span class="hljs-built_in">get</span>&lt;<span class="hljs-type">double</span>&gt;(t1) = <span class="hljs-number">312</span>;      <span class="hljs-comment">// 写入到double</span>
</code></pre>
<p>与<code>pair</code>类似，只要元素允许，<code>tuple</code>也可被赋值和进行算数比对。
与<code>tuple</code>类似，<code>pair</code>也可以通过<code>get&lt;&gt;()</code>访问。</p>
<p>和<code>pair</code>的情况相同，结构化绑定（§3.6.3）也可用于<code>tuple</code>。
不过，在不必使用泛型代码时，带有命名成员的简单结构体通常可以让代码更易于维护。</p>
<h2 id="13.5">13.5 待选项 </h2>
<p>标准库提供了三种类型来表示待选项：</p>
<ul>
<li><code>variant</code>表示一组指定的待选项中的一个(在<code>&lt;variant&gt;</code>中）</li>
<li><code>optional</code>表示某个指定类型的值或者该值不存在（在<code>&lt;optional&gt;</code>中）</li>
<li><code>any</code>表示一组未指定待选类型中的一个（在<code>&lt;any&gt;</code>中）</li>
</ul>
<p>这三种类型为用户提供相似的功能。只可惜它们的接口并不一致。</p>
<p><a class="en-page-number" id="175"></a></p>
<h3 id="13.5.1">13.5.1 <code>variant</code> </h3>
<p><code>variant&lt;A,B,C&gt;</code>通常是<code>union</code>（§2.4）显式应用的更安全、更便捷的替代品。
可能最简单的示例是在值和错误码之间返回其一：</p>
<pre><code class="lang-cpp"><span class="hljs-function">variant&lt;string,<span class="hljs-type">int</span>&gt; <span class="hljs-title">compose_message</span><span class="hljs-params">(istream&amp; s)</span>
</span>{
    string mess;
    <span class="hljs-comment">// ... 从s读取并构造一个消息 ...</span>
    <span class="hljs-keyword">if</span> (no_problems)
        <span class="hljs-keyword">return</span> mess;            <span class="hljs-comment">// 返回一个string</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-keyword">return</span> error_number;    <span class="hljs-comment">// 返回一个int</span>
}
</code></pre>
<p>当你用一个值给<code>variant</code>赋值或初始化，它会记住此值的类型。
后续可以查询这个<code>variant</code>持有的类型并提取此值。例如：</p>
<pre><code class="lang-cpp">    <span class="hljs-keyword">auto</span> m = <span class="hljs-built_in">compose_message</span>(cin);

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">holds_alternative</span>&lt;string&gt;(m)) {
        cout &lt;&lt; m.<span class="hljs-built_in">get</span>&lt;string&gt;();
    }
    <span class="hljs-keyword">else</span> {
        <span class="hljs-type">int</span> err = m.<span class="hljs-built_in">get</span>&lt;<span class="hljs-type">int</span>&gt;();
        <span class="hljs-comment">// ... 处理错误 ...</span>
    }
</code></pre>
<p>这种风格在不喜欢异常（参见 §3.5.3）的群体中颇受欢迎，但还有更有意思的用途。
例如，某个简单的编译器可能需要以不同的表示形式区分不同的节点：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">using</span> Node = variant&lt;Expression,Statement,Declaration,Type&gt;;

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">check</span><span class="hljs-params">(Node* p)</span>
</span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">holds_alternative</span>&lt;Expression&gt;(*p)) {
        Expression&amp; e = <span class="hljs-built_in">get</span>&lt;Expression&gt;(*p);
        <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">holds_alternative</span>&lt;Statement&gt;(*p)) {
        Statement&amp; s = <span class="hljs-built_in">get</span>&lt;Statement&gt;(*p);
        <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-comment">// ... Declaration 和 Type ...</span>
}
</code></pre>
<p>此模式通过检查待选项以决定适当的行为，它非常普遍且相对低效，
故此有必要提供直接的支持：</p>
<p><a class="en-page-number" id="176"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">check</span><span class="hljs-params">(Node* p)</span>
</span>{
    <span class="hljs-built_in">visit</span>(overloaded {
        [](Expression&amp; e) { <span class="hljs-comment">/* ... */</span> },
        [](Statement&amp; s) { <span class="hljs-comment">/* ... */</span> },
        <span class="hljs-comment">// ... Declaration 和 Type ...</span>
    }, *p);
}
</code></pre>
<p>这大体上相当于虚函数调用，但有可能更快。
像所有关于性能的声明一样，在性能至关重要时，应该用测算去验证这个“有可能更快”。
对于多数应用，性能方面的差异并不显著。</p>
<p>很可惜，此处的<code>overloaded</code>是必须且不合标准的。
它是个“小魔法（piece of magic）”，
可以用一组（通常是lambda表达式）的参数构造出一个重载：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span>... Ts&gt;
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">overloaded</span> : Ts... {
    <span class="hljs-function"><span class="hljs-keyword">using</span> <span class="hljs-title">Ts::operator</span><span class="hljs-params">()</span>...</span>;
};

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span>... Ts&gt;
    <span class="hljs-title">overloaded</span><span class="hljs-params">(Ts...)</span> -&gt; overloaded&lt;Ts...&gt;</span>; <span class="hljs-comment">// 推导指南（deduction guide）</span>
</code></pre>
<p>“访问者（visitor）”<code>visit</code>继而对<code>overloaded</code>应用<code>()</code>，
后者依据重载规则选择最适合的lambda表达式进行调用。</p>
<p><em>推导指南（deduction guide）</em>是个用于解决细微二义性的机制，
主要用在基础类库中的模板类构造函数。</p>
<p>如果试图以某个类型访问<code>variant</code>，但与其中保存类型不符时，
将会抛出<code>bad_variant_access</code>。</p>
<h3 id="13.5.2">13.5.2 <code>optional</code> </h3>
<p><code>optional&lt;A&gt;</code>可视作一个特殊的<code>variant</code>（类似一个<code>variant&lt;A,nothing&gt;</code>），
或者是个关于<code>A*</code>概念的推广，它要么指向一个对象，要么是<code>nullptr</code>。</p>
<p>如果一个函数可能返回一个对象也可能不返回时，<code>optional</code>就派上用场了：</p>
<pre><code class="lang-cpp"><span class="hljs-function">optional&lt;string&gt; <span class="hljs-title">compose_message</span><span class="hljs-params">(istream&amp; s)</span>
</span>{
    string mess;
    <span class="hljs-comment">// ... 从s读取并构造一个消息 ...</span>
    <span class="hljs-keyword">if</span> (no_problems)
        <span class="hljs-keyword">return</span> mess;
    <span class="hljs-keyword">return</span> {};  <span class="hljs-comment">// 空 optional</span>
}
</code></pre>
<p>现在，可以这么用：</p>
<p><a class="en-page-number" id="177"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">if</span> (<span class="hljs-keyword">auto</span> m = <span class="hljs-built_in">compose_message</span>(cin))
    cout &lt;&lt; *m;     <span class="hljs-comment">// 请留意这个解引用 (*)</span>
<span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// ... 处理错误 ...</span>
}
</code></pre>
<p>这在不喜欢异常（参见 §3.5.3）的群体中颇受欢迎。请留意这个<code>*</code>古怪的用法。
<code>optional</code>被视为指向对象的指针，而非对象本身。</p>
<p>等效于<code>nullptr</code>的<code>optional</code>是空对象<code>{}</code>。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">cat</span><span class="hljs-params">(optional&lt;<span class="hljs-type">int</span>&gt; a, optional&lt;<span class="hljs-type">int</span>&gt; b)</span>
</span>{
    <span class="hljs-type">int</span> res = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">if</span> (a) res+=*a;
    <span class="hljs-keyword">if</span> (b) res+=*b;
    <span class="hljs-keyword">return</span> res;
}

<span class="hljs-type">int</span> x = <span class="hljs-built_in">cat</span>(<span class="hljs-number">17</span>,<span class="hljs-number">19</span>);
<span class="hljs-type">int</span> y = <span class="hljs-built_in">cat</span>(<span class="hljs-number">17</span>,{});
<span class="hljs-type">int</span> z = <span class="hljs-built_in">cat</span>({},{});
</code></pre>
<p>如果试图访问无值的<code>optional</code>，其结果未定义；而且<em>不</em>会抛出异常。
因此<code>optional</code>无法确保类型安全。</p>
<h3 id="13.5.3">13.5.3 <code>any</code> </h3>
<p><code>any</code>可以持有任意的类型，并在切实持有某个类型的情况下，知道它是什么。
它大体上就是个不受限版本的<code>variant</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-function">any <span class="hljs-title">compose_message</span><span class="hljs-params">(istream&amp; s)</span>
</span>{
    string mess;
    <span class="hljs-comment">// ... 从s读取并构造一个消息 ...</span>
    <span class="hljs-keyword">if</span> (no_problems)
        <span class="hljs-keyword">return</span> mess;            <span class="hljs-comment">// 返回一个 string</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-keyword">return</span> error_number;    <span class="hljs-comment">// 返回一个 int</span>
}
</code></pre>
<p>在你用某个值为一个<code>any</code>赋值或初始化的时候，它会记住该值的类型。
随后可以查询此<code>any</code>持有何种类型并提取其值。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> m = <span class="hljs-built_in">compose_message</span>(cin);
string&amp; s = <span class="hljs-built_in">any_cast</span>&lt;string&gt;(m);
cout &lt;&lt; s;
</code></pre>
<p>如果试图以某个类型访问<code>any</code>，但与其中保存的类型不符时，
将会抛出<code>bad_any_access</code>。
还有些别的方法可以访问<code>any</code>，它们不会抛出异常。</p>
<p><a class="en-page-number" id="178"></a></p>
<h2 id="13.6">13.6 分配器 </h2>
<p>默认情况下，标准库容器使用<code>new</code>分配空间。
操作符<code>new</code>和<code>delete</code>提供通用的自由存储（也叫动态内存或者堆），
该存储区域可存储任意容量的对象，且对象的生命期由用户控制。
这意味着时间和空间的开销在许多特殊情况下可以节省下来。
因此，在必要的时候，标准库容器允许使用特定语意的分配器。
这可以消除一系列烦恼，能够解决的问题有性能（如 内存池分配器）、
安全（回收时擦除内存内容的分配器）、线程内分配器，
以及非一致性内存架构（<a href="https://man7.org/linux/man-pages/man3/numa.3.html" title="详情请参阅链接文档——译者" target="_blank">在特定内存区域上分配，这种内存搭配特定种类的指针</a>）。
尽管它们很重要、非常专业且通常是高级技术，但此处不是讨论这些技术的地方。
不过，我还是要举一个例子，它基于实际的问题，解决方案使用了内存池分配器。</p>
<p>某个重要、长期运行的系统使用事件队列（参见 §15.6），该队列以<code>vector</code>表示事件，
这些事件以<code>shared_ptr</code>的形式传递。因此，一个事件最后的用户隐式地删除了它：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Event</span> {
    vector&lt;<span class="hljs-type">int</span>&gt; data = <span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-number">512</span>);
};

list&lt;shared_ptr&lt;Event&gt;&gt; q;

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">producer</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> n = <span class="hljs-number">0</span>; n!=LOTS; ++n) {
        lock_guardlk{m};    <span class="hljs-comment">// m 是个 mutex （§15.5）</span>
        q.<span class="hljs-built_in">push_back</span>(<span class="hljs-built_in">make_shared</span>&lt;Event&gt;());
        cv.<span class="hljs-built_in">notify_one</span>();
    }
}
</code></pre>
<p>理论上讲这应该工作良好。它在逻辑上很简单，因此代码健壮而易维护。
可惜这导致了内存大规模的碎片化。
在16个生产者和4个消费者间传递了100,000个事件后，消耗了超过6GB的内存。</p>
<p>碎片化问题的传统解决方案是重写代码，使用内存池分配器。
内存池分配器管理固定容量的单个对象且一次性为大量对象分配空间，
而不是一个个进行分配。
好在C++17直接为此提供支持。
内存池分配器定义在<code>std</code>的子命名空间
<code>pmr</code>（“polymorphic memory resource”）中：</p>
<pre><code class="lang-cpp">pmr::synchronized_pool_resource pool;           <span class="hljs-comment">// 创建内存池</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Event</span> {
    vector&lt;<span class="hljs-type">int</span>&gt; data = vector&lt;<span class="hljs-type">int</span>&gt;{<span class="hljs-number">512</span>,&amp;pool};  <span class="hljs-comment">// 让 Events 使用内存池</span>
};
list&lt;shared_ptr&lt;Event&gt;&gt; q {&amp;pool};              <span class="hljs-comment">// 让 q 使用内存池</span>
</code></pre>
<p><a class="en-page-number" id="179"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">producer</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> n = <span class="hljs-number">0</span>; n!=LOTS; ++n) {
        scoped_locklk{m};       <span class="hljs-comment">// m 是个 mutex （§15.5）</span>
        q.<span class="hljs-built_in">push_back</span>(allocate_shared&lt;Event,pmr::polymorphic_allocator&lt;Event&gt;&gt;{&amp;pool});
        cv.<span class="hljs-built_in">notify_one</span>();
    }
}
</code></pre>
<p>现在，16个生产者和4个消费者间传递了100,000个事件后，消耗的内存不到3MB。
这是2000倍左右的提升！当然，实际利用（而非被碎片化浪费的）内存数量没变。
消灭了碎片之后，内存用量随时间推移保持稳定，因此系统可运行长达数月之久。</p>
<p>类似的技术在C++早期就已经取得了良好的效果，但通常需要重写代码才能用于专门的容器。
如今，标准容器通过可选的方式接受分配器参数。
容器的默认内存分配方式是使用<code>new</code>和<code>delete</code>。</p>
<h2 id="13.7">13.7 时间 </h2>
<p>在<code>&lt;chrono&gt;</code>里面，标准库提供了处理时间的组件。
例如，这是给某个事物计时的基本方法：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std::chrono;    <span class="hljs-comment">// 在子命名空间 std::chrono中；参见 §3.4</span>

<span class="hljs-keyword">auto</span> t0 = high_resolution_clock::<span class="hljs-built_in">now</span>();
<span class="hljs-built_in">do_work</span>();
<span class="hljs-keyword">auto</span> t1 = high_resolution_clock::<span class="hljs-built_in">now</span>();
cout &lt;&lt; <span class="hljs-built_in">duration_cast</span>&lt;milliseconds&gt;(t1-t0).<span class="hljs-built_in">count</span>() &lt;&lt; <span class="hljs-string">&quot;msec\n&quot;</span>;
</code></pre>
<p>时钟返回一个<code>time_point</code>（某个时间点）。
把两个<code>time_point</code>相减就得到个<code>duration</code>（一个时间段）。
各种时钟按各自的时间单位给出结果（以上时钟按<code>nanoseconds</code>计时），
因此，把<code>duration</code>转换到一个已知的时间单位通常是个好点子。
这是<code>duration_cast</code>的业务。</p>
<p>未测算过时间的情况下，就别提“效率”这茬。臆测的性能总是不靠谱。</p>
<p>为了简化写法且避免出错，<code>&lt;chrono&gt;</code>提供了时间单位后缀（§5.4.4）。例如：</p>
<pre><code class="lang-cpp">this_thread::<span class="hljs-built_in">sleep</span>(<span class="hljs-number">10</span>ms+<span class="hljs-number">33u</span>s); <span class="hljs-comment">// 等等 10 毫秒和 33 微秒</span>
</code></pre>
<p>时间后缀定义在命名空间<code>std::chrono_literals</code>里。</p>
<p>有个优雅且高效的针对<code>&lt;chrono&gt;</code>的扩展，
支持更长时间间隔（如：年和月）、日历和时区，将被添加到C++20的标准中。
它当前已经存在并被广泛应用了[Hinnant,2018] [Hinnant,2018b]。可以这么写：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> spring_day = apr/<span class="hljs-number">7</span>/<span class="hljs-number">2018</span>;
cout &lt;&lt; <span class="hljs-built_in">weekday</span>(spring_day) &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>; <span class="hljs-comment">// Saturday</span>
</code></pre>
<p>它甚至能处理闰秒。</p>
<p><a class="en-page-number" id="180"></a></p>
<h2 id="13.8">13.8 函数适配 </h2>
<p>以函数作为参数传递的时候，参数类型必须跟被调用函数的参数声明中的要求丝毫不差。
如果预期的参数“差不离儿（almost matches expectations）”，
那有三个不错的替代方案：</p>
<ul>
<li>使用lambda表达式（§13.8.1）。</li>
<li>用<code>std::mem_fn()</code>把成员函数弄成函数对象（§13.8.2）。</li>
<li>定义函数接受一个<code>std::function</code>（§13.8.3）。</li>
</ul>
<p>还有些其它方法，但最佳方案通常是这三者之一。</p>
<h3 id="13.8.1">13.8.1 lambda表达式适配 </h3>
<p>琢磨一下这个经典的“绘制所有图形”的例子：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">draw_all</span><span class="hljs-params">(vector&lt;Shape*&gt;&amp; v)</span>
</span>{
    for_each(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),[](Shape* p) { p-&gt;<span class="hljs-built_in">draw</span>(); });
}
</code></pre>
<p>跟所有标准库算法类似，<code>for_each()</code>使用传统的函数调用语法<code>f(x)</code>调用其参数，
但<code>Shape</code>的<code>draw()</code>却按 OO 惯例写作<code>x-&gt;f()</code>。
lambda表达式轻而易举化解了二者写法的差异。</p>
<h3 id="13.8.2">13.8.2 <code>mem_fn()</code> </h3>
<p>给定一个成员函数，函数适配器<code>mem_fn(mf)</code>输出一个可按非成员函数调用的函数对象。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">draw_all</span><span class="hljs-params">(vector&lt;Shape*&gt;&amp; v)</span>
</span>{
    for_each(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),<span class="hljs-built_in">mem_fn</span>(&amp;Shape::draw));
}
</code></pre>
<p>在lambda表达式被C++11引入之前，<code>men_fn()</code>
及其等价物是把面向对象风格的调用转化到函数式风格的主要方式。</p>
<h3 id="13.8.3">13.8.3 <code>function</code> </h3>
<p>标准库的<code>function</code>是个可持有任何对象并通过调用操作符<code>()</code>调用的类型。
就是说，一个<code>function</code>类型的对象是个函数对象（§6.3.2）。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">f1</span><span class="hljs-params">(<span class="hljs-type">double</span>)</span></span>;
function&lt;<span class="hljs-type">int</span>(<span class="hljs-type">double</span>)&gt; fct1 {f1};                <span class="hljs-comment">// 初始化为 f1</span>

<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">f2</span><span class="hljs-params">(string)</span></span>;
function fct2 {f2};                             <span class="hljs-comment">// fct2 的类型是 function&lt;int(string)&gt;</span>

function fct3 = [](Shape* p) { p-&gt;<span class="hljs-built_in">draw</span>(); };    <span class="hljs-comment">// fct3 的类型是 function&lt;void(Shape*)&gt;</span>
</code></pre>
<p>对于<code>fct2</code>，我让<code>function</code>的类型从初始化器推导：<code>int(string)</code>。</p>
<p><a class="en-page-number" id="181"></a></p>
<p>显然，<code>function</code>用途广泛：用于回调、将运算作为参数传递、传递函数对象等等。
但是与直接调用相比，它可能会引入一些运行时的性能损耗，
而且<code>function</code>作为函数对象无法参与函数重载。
如果需要重载函数对象（也包括lambda表达式），
请考虑使用<code>overloaded</code>（§13.5.1）。</p>
<h2 id="13.9">13.9 类型函数 </h2>
<p><em>类型函数（type function）</em>是这样的函数：它在编译期进行估值，
以某个类型作为参数，或者返回一个类型。
标准库提供丰富的类型函数，用于帮助程序库作者（以及普通程序员）在编码时
能从语言本身、标准库以及普通代码中博采众长。</p>
<p>对于数值类型，<code>&lt;limits&gt;</code>中的<code>numeric_limits</code>提供了诸多有用的信息（§14.7）。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">float</span> min = numeric_limits&lt;<span class="hljs-type">float</span>&gt;::<span class="hljs-built_in">min</span>(); <span class="hljs-comment">// 最小的正浮点数</span>
</code></pre>
<p>同样的，对象容量可以通过内建的<code>sizeof</code>运算符（§1.4）获取。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> szi = <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>);    <span class="hljs-comment">// int的字节数</span>
</code></pre>
<p>这些类型函数是C++编译期计算机制的一部分，跟其它方式相比，
它们提供了更严格的类型检查和更好的性能。
这些特性的应用也被称为<em>元编程（metaprogramming）</em>
或者（在涉及模板的时候）叫<em>模板元编程（template metaprogramming）</em>。
此处，仅对标准库构件列举两个应用：
<code>iterator_traits</code>（§13.9.1）和类型谓词（§13.9.2）。</p>
<p>概束（§7.2）让此技术的一部分变得多余，在余下的那些中还简化了一部分，
但概束尚未进入标准且未得到广泛支持，因此这些技术仍有广泛的应用。</p>
<h3 id="13.9.1">13.9.1 <code>iterator_traits</code> </h3>
<p>标准库的<code>sort()</code>接收一对迭代器用来定义一个序列（第12章）。
此外，这些迭代器必须支持对序列的随机访问，就是说，
它们必须是<em>随机访问迭代器（random-access iterators）</em>。
某些容器，比方说<code>forward_list</code>，不满足这一点。
更有甚者，<code>forward_list</code>是个单链表，因此取下标操作代价高昂，
且缺乏合理的方式去找到前一个元素。
但是，与大多数容器类似，<code>forward_list</code>
提供<em>前向迭代器（forward iterators）</em>，
可供标准算法和 <code>for</code>-语句 对序列遍历（§6.2）。</p>
<p>标准库提供了一个名为<code>iterator_traits</code>的机制，可以对迭代器进行检查。
有了它，就可以改进 §12.8 中的 区间<code>sort()</code>，
使之同时接受<code>vector</code>和<code>forward_list</code>。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">test</span><span class="hljs-params">(vector&lt;string&gt;&amp; v, forward_list&lt;<span class="hljs-type">int</span>&gt;&amp; lst)</span>
</span>{
    <span class="hljs-built_in">sort</span>(v);    <span class="hljs-comment">// 排序vector</span>
    <span class="hljs-built_in">sort</span>(lst);  <span class="hljs-comment">// 排序单链表</span>
}
</code></pre>
<p>能让这段代码生效的技术具有普遍的用途。</p>
<p><a class="en-page-number" id="182"></a></p>
<p>首先要写两个辅助函数，它接受一个额外的参数，
以标示当前用于随机访问迭代器还是前向迭代器。
接收随机访问迭代器的版本无关紧要：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Ran&gt;                                          <span class="hljs-comment">// 用于随机访问迭代器</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">sort_helper</span><span class="hljs-params">(Ran beg, Ran end, random_access_iterator_tag)</span>  <span class="hljs-comment">// 可在[beg:end)范围内取下标</span>
</span>{
    <span class="hljs-built_in">sort</span>(beg,end);  <span class="hljs-comment">// 直接排序</span>
}
</code></pre>
<p>前向迭代器的版本仅仅将列表复制进<code>vector</code>、排序，然后复制回去：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> For&gt;                                      <span class="hljs-comment">// 用于前向迭代器</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">sort_helper</span><span class="hljs-params">(For beg, For end, forward_iterator_tag)</span>    <span class="hljs-comment">// 可在[beg:end)范围内遍历</span>
</span>{
    vector&lt;Value_type&lt;For&gt;&gt; v {beg,end};    <span class="hljs-comment">// 用[beg:end)初始化一个vector</span>
    <span class="hljs-built_in">sort</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>());                <span class="hljs-comment">// 利用随机访问进行排序</span>
    <span class="hljs-built_in">copy</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),beg);            <span class="hljs-comment">// 把元素复制回去</span>
}
</code></pre>
<p><code>Value_type&lt;For&gt;</code>是<code>For</code>的元素类型，被称为它的<em>值类型（value type）</em>。
每个标准库迭代器都有个成员<code>value_type</code>。
我通过定义一个类型别名（§6.4.2）来实现<code>Value_type&lt;For&gt;</code>这种写法：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C&gt;
    <span class="hljs-keyword">using</span> Value_type = <span class="hljs-keyword">typename</span> C::value_type;  <span class="hljs-comment">// C的值类型</span>
</code></pre>
<p>这样，对于<code>vector&lt;X&gt;</code>而言，<code>Value_type&lt;X&gt;</code>就是<code>X</code>。</p>
<p>真正的“类型魔法”在辅助函数的选择上：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C&gt; <span class="hljs-type">void</span> <span class="hljs-title">sort</span><span class="hljs-params">(C&amp; c)</span>
</span>{
    <span class="hljs-keyword">using</span> Iter = Iterator_type&lt;C&gt;;
    <span class="hljs-built_in">sort_helper</span>(c.<span class="hljs-built_in">begin</span>(),c.<span class="hljs-built_in">end</span>(),Iterator_category&lt;Iter&gt;{});
}
</code></pre>
<p>此处用了两个类型函数：
<code>Iterator_type&lt;C&gt;</code>返回<code>C</code>的迭代器类型（即<code>C::iterator</code>），
然后，<code>Iterator_category&lt;Iter&gt;{}</code>构造一个“标签（tag）”值标示迭代器类型：</p>
<ul>
<li><code>std::random_access_iterator_tag</code>： 如果<code>C</code>的迭代器支持随机访问</li>
<li><code>std::forward_iterator_tag</code>：如果<code>C</code>的迭代器支持前向迭代</li>
</ul>
<p>这样，就可以在编译期从这两个排序算法之间做出选择了。
这个技术被称为<em>标签分发（tag dispatch）</em>，
是标准库内外用于提升灵活性和性能的技术之一。</p>
<p><code>Iterator_type</code>可定义如下：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C&gt;
    <span class="hljs-keyword">using</span> Iterator_type = <span class="hljs-keyword">typename</span> C::iterator; <span class="hljs-comment">// C的迭代器类型</span>
</code></pre>
<p>但是，要把这个思路扩展到缺乏成员类型的类型，比方说指针，
标准库对标签分发的支持就要以<code>&lt;iterator&gt;</code>中的<code>iterator_traits</code>形式出现。
为指针所做的特化会是这样：</p>
<p><a class="en-page-number" id="183"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>&gt;
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">iterator_traits</span>&lt;T*&gt; {
    <span class="hljs-keyword">using</span> difference_type = <span class="hljs-type">ptrdiff_t</span>;
    <span class="hljs-keyword">using</span> value_type = T;
    <span class="hljs-keyword">using</span> pointer = T*;
    <span class="hljs-keyword">using</span> reference = T&amp;;
    <span class="hljs-keyword">using</span> iterator_category = random_access_iterator_tag;
};
</code></pre>
<p>现在可以这样写：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Iter&gt;
    <span class="hljs-keyword">using</span> Iterator_category = <span class="hljs-keyword">typename</span> std::iterator_traits&lt;Iter&gt;::iterator_category;   <span class="hljs-comment">// Iter的类别</span>
</code></pre>
<p>现在，尽管<code>int*</code>没有成员类型，仍可以用作随机访问迭代器；
<code>iterator_category&lt;int*&gt;</code>是<code>random_access_iterator_tag</code>。</p>
<p>由于概束（§7.2）的出现，许多 trait 和基于 trait 的技术都变得多余了。
琢磨一下<code>sort()</code>这个概束版本的例子：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;RandomAccessIterator Iter&gt;
<span class="hljs-type">void</span> <span class="hljs-title">sort</span><span class="hljs-params">(Iter p, Iter q)</span></span>; <span class="hljs-comment">// 用于 std::vector 和其它支持随机访问的类型</span>

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;ForwardIterator Iter&gt;
<span class="hljs-type">void</span> <span class="hljs-title">sort</span><span class="hljs-params">(Iter p, Iter q)</span>
    <span class="hljs-comment">// 用于 std::list 和其它仅支持前向遍历的类型</span>
</span>{
    vector&lt;Value_type&lt;Iter&gt;&gt; v {p,q};
    <span class="hljs-built_in">sort</span>(v);                    <span class="hljs-comment">// 使用随机访问排序sort</span>
    <span class="hljs-built_in">copy</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>(),p);
}

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;Range R&gt; <span class="hljs-type">void</span> <span class="hljs-title">sort</span><span class="hljs-params">(R&amp; r)</span>
</span>{
    <span class="hljs-built_in">sort</span>(r.<span class="hljs-built_in">begin</span>(),r.<span class="hljs-built_in">end</span>());    <span class="hljs-comment">// 以适宜的方式排序</span>
}
</code></pre>
<p>精进矣。</p>
<h3 id="13.9.2">13.9.2 类型谓词 </h3>
<p>标准库在<code>&lt;type_trait&gt;</code>里面提供了简单的类型函数，
被称为<em>类型谓词（type predicates）</em>，
为有关类型的一些基本问题提供应答。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-type">bool</span> b1 = std::<span class="hljs-built_in">is_arithmetic</span>&lt;<span class="hljs-type">int</span>&gt;();    <span class="hljs-comment">// 是，int是个算数类型</span>
<span class="hljs-type">bool</span> b2 = std::<span class="hljs-built_in">is_arithmetic</span>&lt;string&gt;(); <span class="hljs-comment">// 否，std::string 不是算数类型</span>
</code></pre>
<p>其它范例包括<code>is_class</code>、<code>is_pod</code>、<code>is_literal_type</code>、
<code>has_virtual_destructor</code>，以及<code>is_base_of</code>。
它们主要在编写模板的时候发挥作用。例如：</p>
<p><a class="en-page-number" id="184"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Scalar&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">complex</span> {
    Scalar re, im;
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">static_assert</span>(<span class="hljs-built_in">is_arithmetic</span>&lt;Scalar&gt;(), <span class="hljs-string">&quot;Sorry, I only support complex of arithmetic types&quot;</span>);
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>为提高可读性，标准库定义了模板别名。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
    <span class="hljs-keyword">constexpr</span> <span class="hljs-type">bool</span> is_arithmetic_v = std::<span class="hljs-built_in">is_arithmetic</span>&lt;T&gt;();
</code></pre>
<p>我对<code>_v</code>后缀的写法不以为然，但这个定义别名的技术却大有作为。
例如，标准库这样定义概束<code>Regular</code>（§12.7）：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>&gt;
    <span class="hljs-keyword">concept</span> Regular = Semiregular&lt;T&gt; &amp;&amp; EqualityComparable&lt;T&gt;;
</code></pre>
<h3 id="13.9.3">13.9.3 <code>enable_if</code> </h3>
<p>常见的类型谓词应用包括<code>static_assert</code>条件、编译期<code>if</code>，以及<code>enable_if</code>。
标准库的<code>enable_if</code>在 按需选择定义 方面应用广泛。
考虑定义一个“智能指针”：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Smart_pointer</span> {
    <span class="hljs-comment">// ...</span>
    T&amp; <span class="hljs-keyword">operator</span>*();
    T&amp; <span class="hljs-keyword">operator</span>-&gt;();    <span class="hljs-comment">// -&gt; 能且仅能在T是类的情况下正常工作</span>
};
</code></pre>
<p>该且仅该<code>T</code>是个类类型时定义<code>-&gt;</code>。
例如，<code>Smart_pointer&lt;vector&lt;T&gt;&gt;</code>就应该有<code>-&gt;</code>，
而<code>Smart_pointer&lt;int&gt;</code>就不该有。</p>
<p>这里没办法用编译期<code>if</code>，因为不在函数里。但可以这么写：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-keyword">class</span> <span class="hljs-title class_">Smart_pointer</span> {
    <span class="hljs-comment">// ...</span>
    T&amp; <span class="hljs-keyword">operator</span>*();
    std::enable_if&lt;<span class="hljs-built_in">is_class</span>&lt;T&gt;(),T&amp;&gt; <span class="hljs-keyword">operator</span>-&gt;();  <span class="hljs-comment">// -&gt; 在且仅在T是类的情况下被定义</span>
};
</code></pre>
<p>如果<code>is_class&lt;T&gt;()</code>为<code>true</code>，<code>operator-&gt;()</code>的返回类型为<code>T&amp;</code>；
否则<code>operator-&gt;()</code>的定义就被忽略了。</p>
<p><code>enable_if</code>的语法有点怪，用起来也有点麻烦，
且在许多情况下会因为概束（§7.2）而显得多余。
不过，<code>enable_if</code>是大量当前模板元编程和众多标准库组件的基础。
它依赖一个名为SFINAE
（“替换失败并非错误（Substitution Failure Is Not An Error）”）的语言特性。</p>
<p><a class="en-page-number" id="185"></a></p>
<h2 id="13.10">13.10 忠告 </h2>
<ul>
<li>[1] 有用的的程序库不一定要庞大或者复杂；§13.1。</li>
<li>[2] 任何需要申请并（显式或隐式）释放的东西都是资源；§13.2。</li>
<li>[3] 利用资源执柄去管理资源（RAII）；§13.2；[CG: R.1]。</li>
<li>[4] 把<code>unique_ptr</code>用于多态类的对象；§13.2.1；[CG: R.20]。</li>
<li>[5] （仅）将<code>shared_ptr</code>用于被共享的对象；§13.2.1；[CG: R.20]。</li>
<li>[6] 优先采用附带专用语意的资源执柄，而非智能指针；§13.2.1。</li>
<li>[7] 优先采用<code>unique_ptr</code>，而非<code>shared_ptr</code>；§5.3，§13.2.1。</li>
<li>[8] 用<code>make_unique()</code>去构造<code>unique_ptr</code>；§13.2.1；[CG: R.22]。</li>
<li>[9] 用<code>make_shared()</code>去构造<code>shared_ptr</code>；§13.2.1；[CG: R.23]。</li>
<li>[10] 优先采用智能指针，而非垃圾回收；§5.3，§13.2.1。</li>
<li>[11] 别使用<code>std::move()</code>；§13.2.2；[CG: ES.56]。</li>
<li>[12] 仅在参数转发时使用<code>std::forward()</code>；§13.2.2。</li>
<li>[13] 将某个对象<code>std::move()</code>或<code>std::forward()</code>之后，
  绝不能再读取它；§13.2.2。</li>
<li>[14] 相较于 指针加数量 的方式，优先使用<code>span</code>§13.3；[CG: F.24]。</li>
<li>[15] 在需要容量为 <code>constexpr</code> 的序列时，采用<code>array</code>；§13.4.1。</li>
<li>[16] 优先采用<code>array</code>，而非内建数组；§13.4.1；[CG: SL.con.2]。</li>
<li>[17] 如果要用到<code>N</code>个二进制位，而<code>N</code>又不一定是内建整数类型位数的时候，
  用<code>bitset</code>；§13.4.2。</li>
<li>[18] 别滥用<code>pair</code>和<code>tuple</code>；具名<code>struct</code>通常能让代码更具可读性；§13.4.3。</li>
<li>[19] 使用<code>pair</code>时，利用模板参数推导或<code>make_pair()</code>，
  以避免冗余的类型指定§13.4.3。</li>
<li>[20] 使用<code>tuple</code>时，利用模板参数推导或<code>make_tuple()</code>，
  以避免冗余的类型指定§13.4.3；[CG: T.44]。</li>
<li>[21] 相较于显式的<code>union</code>，优先采用<code>variant</code>；§13.5.1； [CG: C.181]。</li>
<li>[22] 利用分配器以避免产生内存碎片；§13.6。</li>
<li>[23] 在做效率方面的决定之前，请先测定程序的执行时间；§13.7。</li>
<li>[24] 使用<code>duration_cast</code>为测量结果的输出选择合适的时间单位；§13.7。</li>
<li>[25] 指定<code>duration</code>时，采用适当的时间单位；§13.7。</li>
<li>[26] 在需要传统的函数调用写法的地方，
  请使用<code>men_fn()</code>或lambda表达式将成员函数转换成函数对象；§13.8.2。</li>
<li>[27] 需要把可调用的东西保存起来时，请采用<code>function</code>；§13.8.3。</li>
<li>[28] 编写代码时，可显式依赖于类型的属性；§13.9。</li>
<li>[29] 只要条件允许，就优先使用概束去取代 trait 和<code>enable_if</code>；§13.9。</li>
<li>[30] 使用别名和类型谓词以简化书写；§13.9.1，§13.9.2。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 此引言的出处略复杂，详情请见 <a href="https://quoteinvestigator.com/2010/06/11/time-you-enjoy/" target="_blank">https://quoteinvestigator.com/2010/06/11/time-you-enjoy/</a> ，译法取自网络。—— 译者注<a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch12.html" class="navigation navigation-prev " aria-label="Previous page: 12 算法">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch14.html" class="navigation navigation-next " aria-label="Next page: 14 数值">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":13,"title":"13 实用功能","level":"1.15","depth":1,"next":{"title":"14 数值","level":"1.16","depth":1,"path":"ch14.md","ref":"ch14.md","articles":[]},"previous":{"title":"12 算法","level":"1.14","depth":1,"path":"ch12.md","ref":"ch12.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch13.md","mtime":"2023-02-12T09:23:08.805Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

